#include <iostream>

#include "test_harness.hpp"
 
#include "DDAS_XML_Handler.hpp"
#include "DDAS_DB_Handler.hpp"
#include "ddas_definitions.h"

#include <xercesc/framework/LocalFileFormatTarget.hpp>
#include <xercesc/framework/MemBufFormatTarget.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/dom/DOM.hpp>


XERCES_CPP_NAMESPACE_USE 
using namespace std;

	/// Default constructor
	DDAS_XML_Handler::DDAS_XML_Handler()
	{
		try   
		{ 
			XMLPlatformUtils::Initialize(); 
		}   
		catch( xercesc::XMLException& e )   
		{      
			char* message = XMLString::transcode( e.getMessage() );      
			cerr << "XML toolkit initialization error: " << message << endl;      
			XMLString::release( &message ); 
		}
	}

	/// Default destructor
	DDAS_XML_Handler::~DDAS_XML_Handler()
	{
		try   
		{      
			XMLPlatformUtils::Terminate(); 
		}   
		catch( xercesc::XMLException& e )   
		{      
			char* message = XMLString::transcode( e.getMessage() );      
			cerr << "XML toolkit teardown error: " << message << endl;      
			XMLString::release( &message );   
		}
	}


/**
* Initializes a DOMDocument for a given job
*
* @param doc An unitialized pointer to a DOMDocument pointer
* @param job_id The job in question
* @return Returns 1
*/
	int DDAS_XML_Handler::initResultDocument(DOMDocument** doc, int job_id)
	{
                char job_buffer[128];

                //create a DOMImplementation
                DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(XMLString::transcode("Range"));

		//create document root
		(*doc) = impl->createDocument(0, XMLString::transcode("job_results"), 0);

		//grab document root
                DOMElement* root = (*doc)->getDocumentElement();

                //root's job ID attribute
                DOMAttr* job_id_attr = (*doc)->createAttribute(XMLString::transcode("jobID"));

                //convert job_id to char*
                sprintf( job_buffer, "%i", job_id );

		//trim any whitespace
                XMLString::trim(job_buffer);

		//actually set the value of the jobID attribute
                job_id_attr->setValue(XMLString::transcode(job_buffer));

		//append attribute node to root node
                root->setAttributeNode(job_id_attr);

		return 1;
	}

/**
* Appends the data rows from results to the DOMDocument 
*
* @param doc The pointer to the DOMDocument
* @param results The struct containing the result data from a client machine
* @return Returns 1
*/
	int DDAS_XML_Handler::appendDataRows(DOMDocument* doc, DDAS_Client_Result_Data* results)
	{
		DOMElement* 	data_row;
		DOMAttr* 	sse_attr;
		DOMAttr*	bin_rep_attr;
		DOMAttr*	def_num_attr;		

		double* SSE_ptr;
		char** bin_rep_ptr;
		char** def_num_ptr;

		results->getSSEs(&SSE_ptr);
		results->getBinaryRepresentations(&bin_rep_ptr);
		results->getDefinitionNumbers(&def_num_ptr);

                for( unsigned int i=0; i<results->getRowCount(); i++ )
                {
                        data_row        = doc->createElement(XMLString::transcode("data_row"));
                        sse_attr        = doc->createAttribute(XMLString::transcode("SSE"));
                        bin_rep_attr    = doc->createAttribute(XMLString::transcode("binary_representation"));
                        def_num_attr    = doc->createAttribute(XMLString::transcode("definition_number"));

                        //alloc for sprintf call
                        char* sse_buffer = new char[256];

			//set SSE value
                        sprintf( sse_buffer, "%f", SSE_ptr[i] );

			//trim the binary representation
			string trimmed_br(bin_rep_ptr[i]);
			trimmed_br = trimmed_br.substr(1, strlen(bin_rep_ptr[i])-1);

                        //set attr values
                        sse_attr    ->setValue(XMLString::transcode(sse_buffer));
                        bin_rep_attr->setValue(XMLString::transcode(trimmed_br.c_str()));
                        def_num_attr->setValue(XMLString::transcode(def_num_ptr[i]));

                        //append attribute nodes to data row
                        data_row->setAttributeNode(sse_attr);
                        data_row->setAttributeNode(bin_rep_attr);
                        data_row->setAttributeNode(def_num_attr);

                        //append data row to root      
                        doc->getDocumentElement()->appendChild(data_row);
                        
			//free memory
                        delete[] sse_buffer;
		}

		return 1;
	}

/**
* Appends the regression coefficients from results to the DOMDocument
* 
* @param doc The pointer to the DOMDocument
* @param results The struct containing the result data from a client machine
* @return Returns 1
*/
        int DDAS_XML_Handler::appendCoefficients(DOMDocument* doc, DDAS_Client_Result_Data* results)
        {
		DOMElement* coeff;
		DOMAttr* index_attr;
		DOMAttr* value_attr;
		DOMNodeList* data_rows = doc->getDocumentElement()->getElementsByTagName(XMLString::transcode("data_row"));

		Coefficients** coeff_ptr;

		//for each data row
		for( int i=0; i<results->getRowCount(); i++ )
		{
			//set coefficients for this data row
			results->getCoefficients(&coeff_ptr);

			//set how many coefficients for this data row
			int coeff_size = coeff_ptr[i]->getSize();        	

			//for each coefficient for this data row
			for( int j=0; j<coeff_size; j++)
                	{
                		//create coefficient element
                       		coeff = doc->createElement(XMLString::transcode("coefficient"));

                        	//create attribute nodes
                        	index_attr = doc->createAttribute(XMLString::transcode("index"));
                        	value_attr = doc->createAttribute(XMLString::transcode("value"));

                      		//request some space for conversion
                        	char* index_buffer = new char[128];
                        	char* value_buffer = new char[128];

                        	//store the index of the coefficient
				if(j==0) 
					sprintf( index_buffer, "%s", "y intercept" );
                        	else 	
					sprintf( index_buffer, "%i", j );

				//store the value of coefficient
                       		sprintf( value_buffer, "%f", coeff_ptr[i]->getCoefficient(j) );

                        	//set attr values
                        	index_attr->setValue(XMLString::transcode(index_buffer));
                        	value_attr->setValue(XMLString::transcode(value_buffer));

				//add attr nodes to coeff
                        	coeff->setAttributeNode(index_attr);
                        	coeff->setAttributeNode(value_attr);

                        	//append coefficient to data row
                        	((DOMElement*)data_rows->item(i))->appendChild(coeff);

                       	 	//free memory
                       	 	delete[] index_buffer;
                        	delete[] value_buffer;
        		}
		}

		return 1;
        }

/**
* Performs the actual serialization to file, taking a DOMDocument and saving it to the URL specified
*
* @param doc The pointer to the DOMDocument to be written
* @param URL The location to which the file should be written
* @return Returns 1
*/
        int DDAS_XML_Handler::serializeDOMDocument(DOMDocument* doc, char* URL)
        {
		//
		DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(XMLString::transcode("Range"));

		//create a DOM serializer     
                DOMWriter* writer = ((DOMImplementationLS*)impl)->createDOMWriter();

                //set output type and location
                LocalFileFormatTarget local_file(XMLString::transcode(URL));

                //set some optional formatting options
                if (writer->canSetFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true))
                        writer->setFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true);
                if (writer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true))
                        writer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);

                //actually write the root node to file
                writer->writeNode(&local_file, *(doc->getDocumentElement()) );

                //free resources
                writer->release();

		return 1;
        }

	
/**
* Performs the macro level tasks in order to create a result document from the results struct
*
* @param job_id The job in question
* @param num_results The number of results in the struct
* @param results The struct containing the result data from a client machine
* @return Returns 1
*/
	int DDAS_XML_Handler::createResultDocument( int job_id, int num_results, DDAS_Client_Result_Data* results )
	{
		char buffer[512];
		
		DOMDocument* doc;

		//create the result document
		this->initResultDocument(&doc, job_id);

		//create and append data rows to DOMDocument
		this->appendDataRows(doc, results);

		//create and append coefficients to DOMDocument
		this->appendCoefficients(doc, results);

		//create output file name based on job_id and DDAS_RESULT_URL
		sprintf( buffer, "%sjob_%u_results.xml", DDAS_RESULT_URL, job_id );

		//serialize DOMDocument to output file
		this->serializeDOMDocument(doc, buffer);
	}


        /**
        * Creates a scoping file (XML document) converts it to a char[] and returns it. The parameters are used to populate the XML document.
        *
        * @param start The first regression to be performed
        * @param end The last regression to be performed
        * @param numX The number of independent variables
        * @param numY The number of dependent variables
        * @param numRows The number of rows in the data set
        * @param numResults The number of results to be returned (researcher defined)
        * @param jobID The job ID - which job does this file belong to?
        * @return Returns the scoping file as a char[]
        */
	char* DDAS_XML_Handler::getCustomFileAsString( verylong start, verylong end, int numX, int numY, int numRows, int numResults, int jobID )
	{
                try
                {
                        char* tmp = new char[128];

                        //create a DOM
                        DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(XMLString::transcode("Range"));
                        DOMDocument* doc        = impl->createDocument(0, XMLString::transcode("UncwddasWorkUnit"), 0);
                        DOMElement* root        = doc->getDocumentElement();

                        DOMAttr* start_attr     = doc->createAttribute(XMLString::transcode("start"));
                        DOMAttr* end_attr       = doc->createAttribute(XMLString::transcode("end"));
                        DOMAttr* numX_attr      = doc->createAttribute(XMLString::transcode("numberX"));
                        DOMAttr* numY_attr      = doc->createAttribute(XMLString::transcode("numberY"));
                        DOMAttr* numRows_attr   = doc->createAttribute(XMLString::transcode("numberRows"));
                        DOMAttr* numResults_attr= doc->createAttribute(XMLString::transcode("numberResults"));
                        DOMAttr* jobID_attr     = doc->createAttribute(XMLString::transcode("jobID"));

                        zswrite(tmp, start);


                        XMLCh* start_val = XMLString::transcode( tmp );
                        zswrite(tmp, end);
                        XMLCh* end_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numX);
                        XMLCh* numX_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numY);
                        XMLCh* numY_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numRows);
                        XMLCh* numRows_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numResults);
                        XMLCh* numResults_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", jobID);
                        XMLCh* jobID_val = XMLString::transcode( tmp );

                        start_attr      ->setValue(start_val);
                        end_attr        ->setValue(end_val);
                        numX_attr       ->setValue(numX_val);
                        numY_attr       ->setValue(numY_val);
                        numRows_attr    ->setValue(numRows_val);
                        numResults_attr ->setValue(numResults_val);
                        jobID_attr      ->setValue(jobID_val);

                        root->setAttributeNode(start_attr);
                        root->setAttributeNode(end_attr);
                        root->setAttributeNode(numX_attr);
                        root->setAttributeNode(numY_attr);
                        root->setAttributeNode(numRows_attr);
                        root->setAttributeNode(numResults_attr);
                        root->setAttributeNode(jobID_attr);


                        //DOM serialization
                        DOMWriter* writer = ((DOMImplementationLS*)impl)->createDOMWriter();
			MemBufFormatTarget mbft(8192);	

                        if (writer->canSetFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true))
                                writer->setFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true);
                        if (writer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true))
                                writer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);

                        writer->writeNode( &mbft, *root );

			char* retval = new char[8192];
			
			strcpy(retval, (char*) mbft.getRawBuffer());


                        //free resources
                        writer  ->release();
                        doc     ->release();
                        delete[] tmp;

                        return retval;
                }
                catch( XMLException& e )
                {
			cout<<"XMLException in DDAS_XML_Handler's getCustomFileAsString() method."<<endl;
                        char* message = XMLString::transcode( e.getMessage() );
                        cout << "Error parsing XML document: " << message << endl;
                        XMLString::release( &message );
                }

		return "";
	}


        /**
        * Writes the workunit's scoping file to its proper, hashed/hierarchical location on disk
        *
        * @param file_path The location and the name of the file to be written (e.g. /home/user/data.xml)
        * @param start The first regression to be run
        * @param end The last regression to be run
        * @param numX The number of independent variables
        * @param numY The number of dependent variables
        * @param numRows The number of rows in the data set
        * @param numResults The number of resuls that should be returned by the client (researcher defined)
        * @param jobID The job tow which this scoping file belongs
        * @return Returns 1 if successful, 0 otherwise
        */
	int DDAS_XML_Handler::writeCustomFile( const char* file_path, verylong start, verylong end, int numX, int numY, int numRows, int numResults, int jobID )
	{
		try
		{
			char* tmp = new char[128];

			//create a DOM 
        		DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(XMLString::transcode("Range"));
        		DOMDocument* doc 	= impl->createDocument(0, XMLString::transcode("UncwddasWorkUnit"), 0);
			DOMElement* root  	= doc->getDocumentElement();

			DOMAttr* start_attr    	= doc->createAttribute(XMLString::transcode("start"));
			DOMAttr* end_attr    	= doc->createAttribute(XMLString::transcode("end"));
			DOMAttr* numX_attr	= doc->createAttribute(XMLString::transcode("numberX"));
			DOMAttr* numY_attr    	= doc->createAttribute(XMLString::transcode("numberY"));
			DOMAttr* numRows_attr   = doc->createAttribute(XMLString::transcode("numberRows"));
			DOMAttr* numResults_attr= doc->createAttribute(XMLString::transcode("numberResults"));
			DOMAttr* jobID_attr    	= doc->createAttribute(XMLString::transcode("jobID"));

			zswrite(tmp, start);
			XMLCh* start_val = XMLString::transcode( tmp );
                        zswrite(tmp, end);
                        XMLCh* end_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numX);
                        XMLCh* numX_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numY);
                        XMLCh* numY_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numRows);
                        XMLCh* numRows_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", numResults);
                        XMLCh* numResults_val = XMLString::transcode( tmp );
                        sprintf(tmp, "%i", jobID);
                        XMLCh* jobID_val = XMLString::transcode( tmp );

			start_attr 	->setValue(start_val);
			end_attr	->setValue(end_val);
			numX_attr	->setValue(numX_val);
			numY_attr	->setValue(numY_val);
			numRows_attr	->setValue(numRows_val);
			numResults_attr	->setValue(numResults_val);
			jobID_attr	->setValue(jobID_val);

			root->setAttributeNode(start_attr);
			root->setAttributeNode(end_attr);
			root->setAttributeNode(numX_attr);
			root->setAttributeNode(numY_attr);
			root->setAttributeNode(numRows_attr);
			root->setAttributeNode(numResults_attr);	
			root->setAttributeNode(jobID_attr);

			//DOM serialization	
			DOMWriter* writer = ((DOMImplementationLS*)impl)->createDOMWriter();
			LocalFileFormatTarget local_file( XMLString::transcode(file_path) );

			if (writer->canSetFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true))
				writer->setFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true);
			if (writer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true))
				writer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);			

			writer->writeNode(&local_file, *root);
	

			//free resources
			writer	->release();
			doc	->release();
			delete[] tmp;

			return 1;
		}
		catch( XMLException& e )
		{
			return 0;
		}

		return 0;
	}


        /**
        * Writes the workunit's common data file to its proper, hashed location on disk.  The original data file, 'source_file_path', is copied to 'dest_file_path', which is derived from 'dir_hier_path'. This is fundamentally a file copying function which depends on BOINC's 'dir_hier_path' function to copy a file to its final output location.  The call to 'dir_hier_path' must be made PRIOR to calling this function as this function does not do it for you.
        *
        * @param source_file_path The file path of the source data file (path + name)
        * @param dest_file_path The file path of the destination file path (must call 'dir_hier_path()' to set 'dest_file_path' BEFORE calling this method)
        * @return Return 1 if successful, 0 otherwise
        */
        int DDAS_XML_Handler::writeCommonFile( const char* source_file_path, const char* dest_file_path )
        {
		char* buffer;
		long file_size;
		size_t result;
		FILE* source_file;
		FILE* dest_file;
 
		//open source - fail if unable to open
		source_file = fopen( source_file_path, "r");
		if( source_file==NULL ) return 0;
	
		//open output - fail if unable to open
		dest_file = fopen( dest_file_path, "w");
		if( dest_file == NULL ) return 0;
		
		//find size
		fseek (source_file , 0 , SEEK_END);
  		file_size = ftell (source_file);
  		rewind (source_file);

		//allocate a char buffer - fail if unsuccessful
		buffer = new char[file_size];
		if(buffer == NULL) return 2;

		//read file into char buffer - fail if unsuccessful
		result = fread( buffer, 1, file_size, source_file);
		if(result != file_size) return 3;

		//write buffer to file
    		fwrite(buffer, 1, sizeof(buffer), dest_file);

		//close files
    		fclose(source_file);
		fclose(dest_file);

		//free char buffer
		delete buffer;
	
		return 1;
        }


        /**
        * Converts client's result data from XML file to form appropriate for validation
        *
        * @param result The result from which the location of the client's result data can be derived
        * @return Returns the client's output as a struct of type DDAS_CLIENT_RESULT_DATA*
        */
	DDAS_Client_Result_Data* DDAS_XML_Handler::parseResultData( const RESULT& result )
	{
		string file_path;

                DDAS_DB_Handler* db_handler = new DDAS_DB_Handler();

                DDAS_Client_Result_Data* retval;

		//find the file's location based on its result
        	if( get_output_file_path(result, file_path) )
        	{
                	log_messages.printf
                	(
                        	SCHED_MSG_LOG::MSG_CRITICAL,
                        	"[RESULT#%d %s] check_set: can't get output filename\n",
                        	result.id,
                        	result.name
                	);

                	return NULL;
        	}	
		
		DOMAttr*		bin_rep_attr;
                DOMAttr* 		sse_attr; 
                DOMAttr* 		def_num_attr;
                DOMDocument*            doc;
                DOMNodeList*            data_rows;
		DOMNodeList*		coefficients;
		DOMElement* 		current;
		
		XercesDOMParser* 	parser;
		
		try
                {	
                	XMLCh* data_row_xmlch   = XMLString::transcode("data_row");
			XMLCh* bin_rep_xmlch	= XMLString::transcode("binary");
               		XMLCh* sse_xmlch        = XMLString::transcode("SSE");
                	XMLCh* def_num_xmlch    = XMLString::transcode("definition_number");
			XMLCh* coefficient_xmlch= XMLString::transcode("regression_coefficient");
			XMLCh* coeff_index_xmlch= XMLString::transcode("id");
			XMLCh* coeff_value_xmlch= XMLString::transcode("value");
		
			parser = new XercesDOMParser();
                        parser->setValidationScheme(XercesDOMParser::Val_Always);
                        parser->setDoNamespaces(false);
                        parser->setDoSchema(false);
                        parser->setLoadExternalDTD(false);
                        parser->parse(file_path.c_str());

			doc 		= parser->getDocument();
			data_rows 	= doc->getElementsByTagName(data_row_xmlch);

			//find dimensions of the data set
			unsigned int data_row_count 	= (unsigned int)data_rows->getLength();
			unsigned int coefficient_count 	= (unsigned int) (1+(db_handler->getIndependentVarCountFromResult(result.id)));

			//create a result object
			retval = new DDAS_Client_Result_Data(data_row_count, coefficient_count);

			//for each data row
			for(unsigned int i=0; i<retval->getRowCount(); i++)
			{
				//current data_row
				current 		= (DOMElement*)(data_rows->item(i));
				
				//get current's attribute nodes
				bin_rep_attr		= current->getAttributeNode( bin_rep_xmlch );
				sse_attr 		= current->getAttributeNode( sse_xmlch );
				def_num_attr		= current->getAttributeNode( def_num_xmlch );

				//convert attribute values from XMLCh* to char*
				char* bin_rep_ch 	= XMLString::transcode( bin_rep_attr->getValue() );
				char* sse_ch 		= XMLString::transcode( sse_attr->getValue() );
				char* def_num_ch	= XMLString::transcode( def_num_attr->getValue() );

				//store data in DDAS_CLIENT_RESULT_DATA struct
				retval->setSSE( atof(sse_ch), i ); 
				retval->setBinaryRepresentation( bin_rep_ch, i );  
				retval->setDefinitionNumber( def_num_ch, i); 

				//store the regression coefficients for a single result 	
				coefficients = current->getElementsByTagName(coefficient_xmlch);

				Coefficients** coeff_ptr;

				retval->getCoefficients(&coeff_ptr);

				for( unsigned int j=0; j<coeff_ptr[i]->getSize(); j++ )
				{
					//store the value of the coefficient
					double value = atof( XMLString::transcode( ((DOMElement*)coefficients->item(j))->getAttributeNode(coeff_value_xmlch)->getValue() ) );	
					coeff_ptr[i]->setCoefficient(j, value);		
				}
			}
                }
                catch( xercesc::XMLException& e)
                {
                        char* message = XMLString::transcode( e.getMessage() );
                        cout << "Error parsing XML document: " << message << endl;
                        XMLString::release( &message );
                }

		return retval;
}
